/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.chineseall.app.ws;

import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import jmind.core.collect.FixedArrayBlockingQueue;
import jmind.core.security.MD5;
import jmind.core.spring.SpringBeanLocator;
import jmind.core.util.DataUtil;

import org.apache.commons.lang.time.DateFormatUtils;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.chineseall.app.ChatManager;
import com.chineseall.app.ConnectionManager;
import com.chineseall.app.MTypes;
import com.chineseall.app.pojo.mongo.MMessage;
import com.chineseall.app.service.MachineService;
import com.chineseall.rebirth.api.RebirthApiFactory;
import com.chineseall.rebirth.api.pojo.User;
import com.chineseall.rebirth.api.service.IUserService;
import com.chineseall.rebirth.bridge.app.AppEncrypt;
import com.google.common.collect.Maps;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;

/**
 * tomcat8 & java7
 * http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/chat/ChatAnnotation.java?view=markup
 * 編輯 tomcat/conf/Catalina/localhost/ROOT.xml
 * 
 <?xml version="1.0" encoding="UTF-8"?>
<Context path="" docBase="/Users/wbxie/work/workspace/chineseall/jmind-websocket/target/jmind-websocket-2.0.2-SNAPSHOT"  reloadable="false" antiJARLocking="true" antiResourceLocking="false"></Context>

 * @author wbxie
 * 2013-11-9
 */
//@ServerEndpoint(value = "/ws/{userType}", configurator = CustomConfigurator.class)
@ServerEndpoint(value = "/ws/{userType}")
public class WebSocketEndpoint {

    /**api 秘钥 加密url 参数**/
    public static final String API_SSECRET_KEY = "^jj!ba%go#sh(`@)il95*.k27$";
    private static final Logger logger = LoggerFactory.getLogger(WebSocketEndpoint.class);
    private static final MachineService machineService = SpringBeanLocator.getInstance().getBean(MachineService.class);

    private static final IUserService userService = RebirthApiFactory.getFactory().getApiService(IUserService.class);

    private String clientId;

    public Session session;
    private int uid;

    private String name;

    int index = ConnectionManager.INSTANCE.NO.incrementAndGet();

    public String getClientId() {
        return clientId;
    }

    public int getUid() {
        return uid;
    }

    @OnOpen
    public void open(Session session, EndpointConfig conf, @PathParam("userType") int userType) {

        this.session = session;
        session.getUserProperties().put("openTime", System.currentTimeMillis());
        if (userType != 1) {
            try {
                Map<String, List<String>> maps = session.getRequestParameterMap();

                // String uri = session.getRequestURI().getPath();

                String t = maps.get("t").get(0);
                String sig = maps.get("sig").get(0);
                boolean res = MD5.md5(userType + API_SSECRET_KEY + t).substring(8).equals(sig);
                if (!res) {
                    session.close(new CloseReason(CloseReason.CloseCodes.PROTOCOL_ERROR, "forbid"));

                }
            } catch (Exception e) {
                e.printStackTrace();

            }
        }

    }

    @OnClose
    public void close(Session session, CloseReason reason) {
        if (reason.getCloseCode() != CloseReason.CloseCodes.CANNOT_ACCEPT
                || reason.getCloseCode() != CloseReason.CloseCodes.NO_STATUS_CODE) {
            ConnectionManager.INSTANCE.remove(this);
        }
        this.session = null;

    }

    @OnMessage
    public void message(String message) {
        try {
            parse(message);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @OnMessage
    public void pongMessage(Session session, PongMessage msg) {
        logger.debug("pong message: " + msg);
    }

    @OnError
    public void error(Session session, Throwable e) {
        //    ConnectionManager.INSTANCE.remove(this);
        //    logger.error("@OnError={},{}", e.getMessage(), this);
    }

    private void parse(String message) throws Exception {
        BasicDBObject jo = (BasicDBObject) JSON.parse(message);
        int mtype = (Integer) jo.remove("mtype");
        switch (mtype) {
        case MTypes.CLIENT_OPEN:
            session.getUserProperties().put("info", jo);
            if (!DataUtil.isEmpty(clientId)) {
                logger.error("open clientId is init ws=" + this);
                return;
            }

            if (!DataUtil.isEmpty(jo.getString("mid"))) {
                ObjectId machineId = machineService.getMachineId(jo);
                this.clientId = machineId.toStringMongod();
            }

            MMessage data = new MMessage();
            data.mtype(MTypes.SERVER_OPEN);
            data.clientId(clientId);
            // 回消息给客户端
            SendMessage.send(session, data);
            return;
        case MTypes.CLIENT_LOGIN:
            if (uid > 0) {
                logger.error("login uid is init ws=" + this);
                return;
            }
            fillUid(jo);

            boolean add = ConnectionManager.INSTANCE.add(uid, this);
            if (add && !DataUtil.isEmpty(clientId)) {
                DBObject update = new BasicDBObject();
                update.put("uid", uid);
                update.put("t", new Date());
                machineService.updateMachine(new ObjectId(clientId), update);
            }
            return;
        case MTypes.CLIENT_HEARTBEAT:
            logger.debug(message);
            data = new MMessage();
            data.mtype(MTypes.SERVER_HEARTBEAT);
            data.clientId(clientId);
            SendMessage.send(session, data);
            return;
        case MTypes.CHAT_CLIENT_LOGIN:
            fillUid(jo);
            int groupId = jo.getInt("gid");
            ChatManager.INSTANCE.add(groupId, uid);
            data = new MMessage();
            data.mtype(MTypes.CHAT_SERVER_LOGIN).code(0);
            SendMessage.send(session, data);
            // 发送之前消息
            FixedArrayBlockingQueue<MMessage> groupMessage = ChatManager.INSTANCE.getGroupMessage(groupId);
            for (MMessage m : groupMessage) {
                SendMessage.send(session, m);
            }
            return;
        case MTypes.CHAT_CLIENT_LOGOUT:
            ChatManager.INSTANCE.remove(jo.getInt("gid"), uid);
            data = new MMessage();
            data.mtype(MTypes.CHAT_SERVER_LOGOUT).code(0);
            SendMessage.send(session, data);
            return;
        case MTypes.CHAT_CLIENT_MESSAGE:
            int gid = jo.getInt("gid");
            data = new MMessage();
            data.mtype(MTypes.CHAT_SERVER_MESSAGE).body(jo.getString("body"));
            data.put("uid", uid);
            data.put("name", name);
            data.put("time", System.currentTimeMillis());
            ChatManager.INSTANCE.sends(gid, data);
            return;
        default:
            break;
        }

    }

    private void fillUid(BasicDBObject jo) {
        if (this.uid == 0) {
            this.uid = AppEncrypt.getUserIdByToken(jo.getString("tokenId"));
            // debug
            session.getUserProperties().put("uid", uid);
            User user = userService.getByUid(uid);
            if (user != null) {
                this.name = user.getNickName();
            }
        }

    }

    public String toString() {
        Map<String, Object> map = Maps.newHashMap();
        map.put("NO", index);
        map.put("uid", uid);
        map.put("clientId", clientId);
        if (session != null) {
            map.put("isOpen", session.isOpen());
            map.put("sessionId", session.getId());
            Map<String, Object> map2 = session.getUserProperties();
            map.put("info", map2.get("info"));
            map.put("openTime", DateFormatUtils.format((Long) map2.get("openTime"), "MM-dd HH:mm:ss"));
        }

        return map.toString();
    }
}
